﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using NHibernate.Cfg;
using DataStore.Domain;
using NHibernate.Tool.hbm2ddl;
using NHibernate;
using log4net;
using System.IO;
using System.Globalization;
using System.Reflection;

namespace DataStore
{
    /// <summary>
    /// Database manager class
    /// </summary>
    public partial class Database : IDatabase
    {
        const string EmptyDatabaseName = "emptydatabase.bcr";

        /// <summary>
        /// Gets the path to the empty database.
        /// </summary>
        /// <value>The empty database path.</value>
        public string EmptyDatabasePath
        {
            get
            {
                string dataPath = GetApplicationDataPath();
                dataPath = System.IO.Path.Combine(dataPath, EmptyDatabaseName);

                return dataPath;
            }
        }

        /// <summary>
        /// Database event logger
        /// </summary>
        static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);

        /// <summary>
        /// Sync object
        /// </summary>
        private static readonly object Lockable = new object();

        /// <summary>
        /// NHibernate connection string
        /// </summary>
        private const string ConnectionStringProperty = "connection.connection_string";
        /// <summary>
        /// Settings file name
        /// </summary>
        public const string SettingsFile = "settings.cfg";

        /// <summary>
        /// Bookshelf session factory cache
        /// </summary>
        protected static Dictionary<string, ISessionFactory> _bookshelfSessionFactory = new Dictionary<string, ISessionFactory>();
        /// <summary>
        /// Configurations session factory
        /// </summary>
        protected static ISessionFactory _configurationSessionFactory;

        /// <summary>
        /// Gets the application settings path.
        /// </summary>
        /// <value>The application settings path.</value>
        public string ApplicationSettingsPath
        {
            get
            {
                string applicationDataPath = GetApplicationDataPath();
                string applicationSettingsFile = System.IO.Path.Combine(applicationDataPath, SettingsFile);

                return applicationSettingsFile;
            }
        }

        static Configuration _configuation;

        /// <summary>
        /// Gets or sets the path to the bookshelf.
        /// </summary>
        /// <value>The path.</value>
        public virtual string Path { get; set; }

        /// <summary>
        /// Change database
        /// </summary>
        /// <param name="path"></param>
        public void ChangeDatabase(string path)
        {
            ChangeDatabase(path, false);
        }

        /// <summary>
        /// Change database
        /// </summary>
        /// <param name="path">The path.</param>
        /// <param name="reinit">if set to <c>true</c> [reinit].</param>
        public virtual void ChangeDatabase(string path, bool reinit)
        {
            Path = path;

            // Session factory not exists
            if (!string.IsNullOrEmpty(Path))
            {
                OpenBookshelf(Path, reinit);
            }
        }


        /// <summary>
        /// Creates a unit of work
        /// </summary>
        /// <returns>A unit of work</returns>
        public virtual UnitOfWork CreateUnitOfWork()
        {
            var session = _bookshelfSessionFactory[this.Path].OpenSession();
            return new UnitOfWork(session);
        }

        /// <summary>
        /// Resets this instance.
        /// </summary>
        public void Reset()
        {
            lock (Lockable)
            {
                _configurationSessionFactory = null;
                foreach (var factory in _bookshelfSessionFactory.Values)
                {
                    factory.Dispose();
                }
                _bookshelfSessionFactory = new Dictionary<string, ISessionFactory>();
                Log.Debug("Database proxy reset");
            }
        }

        /// <summary>
        /// Creates a new application settings factory if not created previously.
        /// Singleton functionality.
        /// </summary>
        /// <param name="reinit">if set to <c>true</c> [reinit].</param>
        /// <returns></returns>
        public ISessionFactory AppSettingsFactory(bool reinit)
        {

            if (_configurationSessionFactory == null || reinit)
            {

                string applicationSettingsFile = ApplicationSettingsPath;
                
                if(!Directory.Exists(GetApplicationDataPath()))
                {
                    Directory.CreateDirectory(GetApplicationDataPath());
                }
                Configuration configuration = GetApplicationConfiguration(GetApplicationDataPath());
                configuration.SetProperty(ConnectionStringProperty, GetApplicationSettingsPath());

                // Create file or overwrite schema
                if (!System.IO.File.Exists(applicationSettingsFile) || reinit)
                {
                    new SchemaExport(configuration).Execute(false, true, false);
                }

                _configurationSessionFactory = configuration.BuildSessionFactory();
            }

            return _configurationSessionFactory;
        }

        /// <summary>
        /// Creates a new application settings factory if not created previously.
        /// Singleton functionality.
        /// </summary>
        /// <returns></returns>
        public ISessionFactory AppSettingsFactory()
        {
            return AppSettingsFactory(false);
        }

        /// <summary>
        /// Creates a new bookshelf factory if not created previously.
        /// Singleton functionality.
        /// </summary>
        /// <param name="path">The path to the bookshelf.</param>
        /// <param name="reinit">if set to <c>true</c> [reinit].</param>
        /// <returns></returns>
        public ISessionFactory BookshelfFactory(string path, bool reinit)
        {

            bool createDefaultData = false;
            if (!_bookshelfSessionFactory.Keys.Contains(path) || reinit)
            {
                Log.Debug(string.Format(CultureInfo.InvariantCulture, "Bookshelf path: {0}", path));

                Configuration configuration = GetApplicationConfiguration(path);
                configuration.SetProperty(ConnectionStringProperty, GetBookshelfPath(path));

                _bookshelfSessionFactory[path] = configuration.BuildSessionFactory();  

                if (!File.Exists(path) || reinit)
                {
                    CreateEmptyDatabase();
                }
            }

            return _bookshelfSessionFactory[path];
        }

        /// <summary>
        /// Creates a new bookshelf factory if not created previously.
        /// Singleton functionality.
        /// </summary>
        /// <param name="path">The path to the bookshelf.</param>
        /// <returns></returns>
        public ISessionFactory BookshelfFactory(string path)
        {

            return BookshelfFactory(path, false);
        }

        /// <summary>
        /// Creates a new bookshelf factory if not created previously.
        /// Singleton functionality.
        /// </summary>
        /// <returns></returns>
        public ISessionFactory BookshelfFactory()
        {

            return BookshelfFactory(Path, false);
        }

        /// <summary>
        /// Opens the bookshelf.
        /// </summary>
        /// <param name="path">The path.</param>
        /// <param name="reinit">if set to <c>true</c> [reinit].</param>
        /// <returns></returns>
        public void OpenBookshelf(string path, bool reinit)
        {
            BookshelfFactory(path, reinit);            
        }

        /// <summary>
        /// Opens the bookshelf.
        /// </summary>
        /// <param name="path">The path.</param>
        /// <returns></returns>
        public void OpenBookshelf(string path)
        {
            OpenBookshelf(path, false);
        }

        /// <summary>
        /// Opens the bookshelf.
        /// </summary>
        /// <returns></returns>
        public void OpenBookshelf()
        {
            OpenBookshelf(Path);
        }


        /// <summary>
        /// Opens the appllication settings.
        /// </summary>
        /// <param name="reinit">if set to <c>true</c> [reinit].</param>
        /// <returns></returns>
        public ISession OpenAppSettings(bool reinit)
        {
            return AppSettingsFactory(reinit).OpenSession();
        }

        /// <summary>
        /// Opens the appllication settings.
        /// </summary>
        /// <returns></returns>
        public ISession OpenAppSettings()
        {
            return AppSettingsFactory(false).OpenSession();
        }

        /// <summary>
        /// Gets the application settings path.
        /// </summary>
        /// <returns></returns>
        public virtual string GetApplicationSettingsPath()
        {
            return string.Format(CultureInfo.InvariantCulture,
                                 @"Data Source={0}\{1};Version=3",
                                 GetApplicationDataPath(),
                                 SettingsFile);
        }

        public virtual string GetBookshelfPath(string bookshelfPath)
        {
            return string.Format(CultureInfo.InvariantCulture,
                                 @"Data Source={0};Version=3",
                                 bookshelfPath);
        }

        /// <summary>
        /// Gets the application configuration.
        /// </summary>
        /// <param name="applicationDataPath">The application data path.</param>
        /// <returns></returns>
        public virtual Configuration GetApplicationConfiguration(string applicationDataPath)
        {

            if(_configuation == null)
            {
                NHibConfigHandler configHandler = new NHibConfigHandler();
                
                // Load exisiting config serialization file
                _configuation = configHandler.LoadConfigurationFromFile();
                if (_configuation != null) 
                {
                    return _configuation;
                }

                // Generate new configuration
                string datasource = GetApplicationSettingsPath();
                Log.Debug(string.Format(CultureInfo.InvariantCulture, "Configuration file data source: {0}", datasource));
                var configuration = new Configuration();
                configuration.Configure();
                configuration.AddAssembly(typeof (Book).Assembly);
                _configuation = configuration;

                // Save new config serialization file
                configHandler.SaveConfigurationToFile(configuration);
            }

            return _configuation;
        }

        /// <summary>
        /// Gets the application data path.
        /// </summary>
        /// <returns></returns>
        public virtual string GetApplicationDataPath()
        {
            return string.Format(CultureInfo.InvariantCulture,
                                 @"{0}\{1}",
                                 System.Environment.GetFolderPath(System.Environment.SpecialFolder.ApplicationData),
                                 Properties.Settings.Default.ApplicationDataFolder);
        }

        public void ExportSchema(Configuration configuration, ISession session)
        {
            IDbConnection connection = session.Connection;
            new SchemaExport(configuration).Execute(false, true, false, connection, null);
        }

        /// <summary>
        /// Generates an empty database
        /// </summary>
        public virtual void GenerateEmptyDatabase()
        {


                // Set empty database path
                string originalPath = Path;
                ChangeDatabase(EmptyDatabasePath);

                // Export schema, write default data
                Configuration configuration = GetApplicationConfiguration(EmptyDatabasePath);
                using (UnitOfWork unitOfWork = CreateUnitOfWork())
                {
                    ExportSchema(configuration, unitOfWork.Session);
                    DefaultData.Write(unitOfWork);
                }

                // Restore path
                Path = originalPath;
            
        }


        /// <summary>
        /// Creates an empty database at the current path.
        /// </summary>
        public virtual void CreateEmptyDatabase()
        {
            var fileInfo = new FileInfo(EmptyDatabasePath);
            if (!fileInfo.Exists || fileInfo.Length == 0)
            {
                GenerateEmptyDatabase();
            }
            
            // Copy empty database
            if (EmptyDatabasePath != Path)
            {
                FileManager.Copy(EmptyDatabasePath, Path);
            }
            
        }


    }
}
